/*
 *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree.
 */
'use strict';

const STUN_URL = "stun:stun.counterpath.net:3478";
const TURN_URL = "turn:turn.gobelieve.io:3478?transport=udp";

var fs = require('fs');
const crypto = require('crypto');

var kRTCICECandidateTypeKey = "type";
var kRTCICECandidateTypeValue = "candidate";
var kRTCICECandidateMidKey = "id";
var kRTCICECandidateMLineIndexKey = "label";
var kRTCICECandidateSdpKey = "candidate";
var kRTCICECandidatesTypeKey = "candidates";

var configuration = {
    'iceServers': [{
        'url': 'stun:stun.counterpath.net:3478'
    }]
};
var kChunkSize = 16384;


function trace(m) {
    console.log(m);
}

//ECMAScript 5 Object.create(o)
function object(o) {
    var F = function() {};
    F.prototype = o;
    return new F();
}

function P2PFile() {
    this.channelID = "";
    this.logError = this.logError.bind(this);
    this.localDescCreated = this.localDescCreated.bind(this);
    this.onIceCandidate = this.onIceCandidate.bind(this);
}

P2PFile.prototype.onIceCandidate = function(evt) {
    var im = this.im;
    var uid = this.uid;
    var peer = this.peer;
    var channelID = this.channelID;
    if (evt.candidate) {
        var candidate = {channel_id:channelID};
        console.log("origin candidate:" + JSON.stringify(evt.candidate));
        candidate[kRTCICECandidateTypeKey] = kRTCICECandidateTypeValue;

        candidate[kRTCICECandidateMLineIndexKey] = evt.candidate.sdpMLineIndex;
        candidate[kRTCICECandidateMidKey] = evt.candidate.sdpMid;
        candidate[kRTCICECandidateSdpKey] = evt.candidate.candidate;

        var content = JSON.stringify({
            p2p: candidate
        });
        console.log("local candidate:" + content);

        var msg = {
            sender: uid,
            receiver: peer,
            content: content
        };
        im.sendRTMessage(msg);
    }
}

P2PFile.prototype.localDescCreated = function(desc) {
    console.log("set local description");

    var pc = this.pc;
    var peer = this.peer;
    var uid = this.uid;
    var channelID = this.channelID;
    pc.setLocalDescription(desc, function() {
        var obj = {};
        obj.channel_id = channelID;
        obj.sdp = pc.localDescription.sdp;
        obj.type = pc.localDescription.type;
        console.log("local desc:" + pc.localDescription)
        console.log("local desc:" + JSON.stringify(obj));

        var msg = {};
        msg.sender = uid;
        msg.receiver = peer;
        msg.content = JSON.stringify({
            p2p: obj
        });
        var r = im.sendRTMessage(msg);
        console.log("send rt message:" + r);

    }, this.logError);
}

P2PFile.prototype.handleP2PMessage = function(obj) {
    if (!this.pc) {
        console.log("peer connection is null");
        return;
    }
    var pc = this.pc;
    if (obj.type == "candidate") {
        var m = {
            "sdpMid": obj.id,
            "sdpMLineIndex": obj.label,
            "candidate": obj.candidate
        }
        console.log("pc signaling state:", pc.signalingState, pc.remoteDescription);

        this.addIceCandidate(new RTCIceCandidate(m), function(err) {
            if (err) {
                console.log("add ice err:", err);
            } else {
                console.log("add ice success");
            }
        });
    } else if (obj.type == "remove-candidates") {

    } else if (obj.type == "offer") {
        var sd = new RTCSessionDescription(obj);
        console.log("set remote offer description")
        var self = this;
        pc.setRemoteDescription(sd, function() {
            //在add local stream之后才可以create answer
            console.log("create answer...");
            pc.createAnswer(self.localDescCreated, function(err) {
                console.log("err:", err);
            });
        });
    } else if (obj.type == "answer") {
        var sd = new RTCSessionDescription(obj);
        console.log("set remote answer description");
        pc.setRemoteDescription(sd, function() {

        });
    }
}

P2PFile.prototype.bufferizeCandidates = function(pc, onerror) {
    var candidatesQueue = []

    pc.addEventListener('signalingstatechange', function () {
        if (this.signalingState === 'stable') {

            while (candidatesQueue.length) {
                console.log("flush queue");
                var entry = candidatesQueue.shift()

                this.addIceCandidate(entry.candidate, entry.callback, entry.callback)
            }
        }
    })

    return function (candidate, callback) {
        callback = callback || onerror

        switch (pc.signalingState) {
            case 'closed':
                callback(new Error('PeerConnection object is closed'))
                break
            case 'stable':
                if (pc.remoteDescription) {
                    pc.addIceCandidate(candidate, callback, callback)
                    break
                }
                //passthrough
            default:
                console.log("queue candidate");
                candidatesQueue.push({
                    candidate: candidate,
                    callback: callback
                })
        }
    }
}

P2PFile.prototype.logError = function(error) {
    console.log(error.name + ': ' + error.message);
}


function P2PFileSender(filePath, fileSize) {
    P2PFile.call(this);
    this.onSendChannelOpen = this.onSendChannelOpen.bind(this);
    this.onSendChannelClose = this.onSendChannelClose.bind(this);
    this.filePath = filePath;
    this.fileSize = fileSize;
    this.sendedSize = 0;
}

P2PFileSender.prototype = object(P2PFile.prototype);
P2PFileSender.prototype.constructor = P2PFileSender;


P2PFileSender.prototype.createConnection = function() {
    var configuration = {
        'iceServers': [{
            'url': STUN_URL
        }]
    };
    var turnServer = {"url":TURN_URL}
    turnServer.username = "1466_" + uid;
    turnServer.credential = token;
    configuration.iceServers.push(turnServer);

    var localConnection = new RTCPeerConnection(configuration);
    trace('Created local peer connection object localConnection');

    var sendChannel = localConnection.createDataChannel('sendDataChannel');
    sendChannel.binaryType = 'arraybuffer';
    trace('Created send data channel');

    sendChannel.onopen = this.onSendChannelOpen;
    sendChannel.onclose = this.onSendChannelClose;
    localConnection.onicecandidate = this.onIceCandidate;
    localConnection.createOffer()
        .then(this.localDescCreated, this.logError);


    this.pc = localConnection;
    this.addIceCandidate = this.bufferizeCandidates(this.pc);
    this.sendChannel = sendChannel;
}

P2PFileSender.prototype.onSendChannelOpen = function() {
    var readyState = this.sendChannel.readyState;
    trace('Send channel state is: ' + readyState);
    if (readyState === 'open') {
        this.sendData();
    }
}

P2PFileSender.prototype.onSendChannelClose = function() {
    trace('Send channel state is closed ');
}

P2PFileSender.prototype.sendData = function() {
    var self = this;
    var sendChannel = this.sendChannel;
    //"/Users/houxh/production/goubuli/goubuli/android/轻言1.3.apk";
    var filePath = this.filePath;
    var f = fs.openSync(filePath, "r");
    var stat = fs.fstatSync(f);
    var fileSize = stat.size;
    var buf = new Buffer(kChunkSize);
    var offset = 0;
    var hash = crypto.createHash('md5');

    if (fileSize != this.fileSize) {
        console.log("invalid file size:", this.fileSize, fileSize);
        //todo alert error
    }
    console.log("begin send file:", Date.now());
    var sliceFile = function() {
        if (sendChannel.bufferedAmount > sendChannel.bufferedAmountLowThreshold) {
            window.setTimeout(sliceFile, 1000);
            return;
        }
        var bytesRead = fs.readSync(f, buf, 0, kChunkSize, offset);
        console.log("read file offset:", offset, bytesRead);
        if (bytesRead <= 0) {
            console.log("read file eof/error");
            return;
        }

        var data = buf.buffer.slice(0, bytesRead);
        sendChannel.send(data);
        hash.update(buf.slice(0, bytesRead));
        self.sendedSize = offset + bytesRead;
        if (self.onsended) {
            self.onsended(offset + bytesRead);
        }
        if (fileSize > offset + bytesRead) {
            offset = offset + bytesRead;
            window.setTimeout(sliceFile, 0);
        } else {
            var md5 = hash.digest('hex');
            console.log("file send completed:", Date.now(), md5);
            if (self.oncompleted) {
                self.oncompleted(md5);
            }
        }
    };

    sendChannel.onbufferedamountlow = sliceFile;

    sliceFile();
}

P2PFileSender.prototype.closeDataChannels = function() {
    if (this.sendChannel) {
        trace('Closed data channel with label: ' + this.sendChannel.label);
        this.sendChannel.close();
        this.sendChannel = null;
    }
    if (this.pc) {
        this.pc.close();
        this.pc = null;
        trace('Closed peer connection');
    }
}


function P2PFileReceiver(filePath, fileSize) {
    P2PFile.call(this);

    this.receivedSize = 0;
    this.bitrateMax = 0;
    this.receiveBuffer = [];

    this.bytesPrev = 0;
    this.timestampPrev = 0;
    this.timestampStart = 0;;
    this.statsInterval = null;

    this.receiveChannelCallback = this.receiveChannelCallback.bind(this);
    this.onReceiveMessageCallback = this.onReceiveMessageCallback.bind(this);
    this.onReceiveChannelOpen = this.onReceiveChannelOpen.bind(this);
    this.onReceiveChannelClose = this.onReceiveChannelClose.bind(this);

    this.fd = fs.openSync(filePath, "wx");
    this.filePath = filePath;
    this.fileSize = fileSize;
    this.hash = crypto.createHash('md5');
}


P2PFileReceiver.prototype = object(P2PFile.prototype);
P2PFileReceiver.prototype.constructor = P2PFileReceiver;

P2PFileReceiver.prototype.createConnection = function() {
    var configuration = {
        'iceServers': [{
            'url': STUN_URL
        }]
    };
    var turnServer = {"url":TURN_URL}
    turnServer.username = "1466_" + uid;
    turnServer.credential = token;
    configuration.iceServers.push(turnServer);
    var remoteConnection = new RTCPeerConnection(configuration);
    trace('Created remote peer connection object remoteConnection');

    remoteConnection.onicecandidate = this.onIceCandidate;
    remoteConnection.ondatachannel = this.receiveChannelCallback;
    this.pc = remoteConnection;
    this.addIceCandidate = this.bufferizeCandidates(this.pc);
}


P2PFileReceiver.prototype.receiveChannelCallback = function(event) {
    trace('Receive Channel Callback');
    var receiveChannel = event.channel;
    receiveChannel.binaryType = 'arraybuffer';
    receiveChannel.onmessage = this.onReceiveMessageCallback;
    receiveChannel.onopen = this.onReceiveChannelOpen;
    receiveChannel.onclose = this.onReceiveChannelClose;
    this.receiveChannel = receiveChannel;
}

P2PFileReceiver.prototype.onReceiveMessageCallback = function(event) {
    trace('Received Message ' + event.data.byteLength, this.fileSize, this.receivedSize);

    var buf = new Buffer(event.data);

    var bytesWritten = fs.writeSync(this.fd, buf);

    if (bytesWritten != buf.length) {
        console.log("file write error:", buf.length, bytesWritten);
        //todo alert error
        if (this.onerror) {
            this.onerror("disk error");
        }
    }
    this.receivedSize += event.data.byteLength;
    this.hash.update(buf);

    if (this.onreceived) {
        this.onreceived(this.receivedSize);
    }

    if (this.receivedSize === this.fileSize) {
        fs.closeSync(this.fd);
        this.fd = undefined;

        var md5 = this.hash.digest('hex');
        console.log("receive completed:", md5);

        if (this.statsInterval) {
            window.clearInterval(this.statsInterval);
            this.statsInterval = null;
        }

        this.closeDataChannels();
        if (this.oncompleted) {
            this.oncompleted(md5);
        }
    }
}

P2PFileReceiver.prototype.onReceiveChannelOpen = function() {
    var readyState = this.receiveChannel.readyState;
    trace('Receive channel state is: ' + readyState);
    if (readyState === 'open') {
        this.timestampStart = (new Date()).getTime();
        this.timestampPrev = this.timestampStart;

        var displayStats = this.displayStats.bind(this);
        this.statsInterval = window.setInterval(displayStats, 500);

        window.setTimeout(displayStats, 100);
        window.setTimeout(displayStats, 300);
    }
}

P2PFileReceiver.prototype.onReceiveChannelClose = function() {
    trace('Receive channel state is closed ')
}

// display bitrate statistics.
P2PFileReceiver.prototype.displayStats = function() {
    var self = this;
    var remoteConnection = this.pc;
    if (remoteConnection && remoteConnection.iceConnectionState === 'connected') {
        // TODO: once https://code.google.com/p/webrtc/issues/detail?id=4321
        // lands those stats should be preferrred over the connection stats.
        remoteConnection.getStats(null, function(stats) {
            for (var key in stats) {
                var res = stats[key];
                if (self.timestampPrev === res.timestamp) {
                    return;
                }
                if (res.type === 'googCandidatePair' &&
                    res.googActiveConnection === 'true') {
                    // calculate current bitrate
                    console.log("ttttt:", res.timestamp, (res.timestamp - self.timestampPrev), Date.now());
                    var bytesNow = res.bytesReceived;
                    var bitrate = Math.round((bytesNow - self.bytesPrev) * 8 /
                        (res.timestamp - self.timestampPrev));
                    console.log("bitrate:" + bitrate + ' kbits/sec');
                    if (self.onbitrate) {
                        self.onbitrate(bitrate);
                    }
                    self.timestampPrev = res.timestamp;
                    self.bytesPrev = bytesNow;
                    if (bitrate > self.bitrateMax) {
                        self.bitrateMax = bitrate;
                    }
                }
            }
        });
    }
}


P2PFileReceiver.prototype.closeDataChannels = function() {
  if (this.receiveChannel) {
    trace('Closed data channel with label: ' + this.receiveChannel.label);
    this.receiveChannel.close();
    this.receiveChannel = null;
  }
  this.pc.close();
  this.pc = null;
  trace('Closed peer connection');
}



module.exports.P2PFileSender = P2PFileSender;
module.exports.P2PFileReceiver = P2PFileReceiver;
